package net.gdface.utils;

import static com.google.common.base.Preconditions.checkArgument;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;

/**
 * 对象类型相关工具
 * @author guyadong
 *
 */
public class TypeUtils {

	static final FullName FULLNAME_INSTANCE_TRUE = new FullName() {
		@Override
		public boolean isFullName(Class<?> type) {
			return true;
		}
	};
	private static final FullName FULLNAME_INSTANCE_FALSE = new FullName() {
		@Override
		public boolean isFullName(Class<?> type) {
			return true;
		}
	};
	public TypeUtils() {
	}
	public static interface Action{
		void doClass(Class<?> type);
	}
	public static void traverseTypes(Type type,Action action){
		checkArgument(null !=action,"action is null");
		if(type instanceof Class<?>){
			action.doClass((Class<?>) type);
		}else if( type instanceof ParameterizedType){
			ParameterizedType paramType = (ParameterizedType)type;
			Type rawType = paramType.getRawType();
			Type[] typeArgs = paramType.getActualTypeArguments();
			traverseTypes(rawType,action);
			for(Type arg:typeArgs){
				traverseTypes(arg,action);
			}
		}else if (type instanceof GenericArrayType) {
			traverseTypes(((GenericArrayType) type).getGenericComponentType(),action);
		} else if (type instanceof TypeVariable) {
			for (Type t : ((TypeVariable<?>) type).getBounds()) {
				traverseTypes(t,action);
			}
		} else if (type instanceof WildcardType) {
			for (Type t : ((WildcardType) type).getLowerBounds()) {
				traverseTypes(t,action);
			}
			for (Type t : ((WildcardType) type).getUpperBounds()) {
				traverseTypes(t,action);
			}
		} else{
			throw new IllegalArgumentException(String.format("not allow type %s", type.toString()));
		}
	}
	@SuppressWarnings("serial")
	public static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
		  return new TypeToken<Map<K, V>>() {}
		    .where(new TypeParameter<K>() {}, keyToken)
		    .where(new TypeParameter<V>() {}, valueToken);
	}
	@SuppressWarnings("serial")
	public static <T> TypeToken<List<T>> listToken(TypeToken<T> keyToken) {
		  return new TypeToken<List<T>>() {}
		    .where(new TypeParameter<T>() {}, keyToken);
	}
	@SuppressWarnings("serial")
	public static <T> TypeToken<Set<T>> setToken(TypeToken<T> keyToken) {
		  return new TypeToken<Set<T>>() {}
		    .where(new TypeParameter<T>() {}, keyToken);
	}
	public final static String getTypeName(Type type, boolean fullClassName) {
		FullName fn = toFullName(fullClassName);
		return getTypeName(type,fn);
	}
	public final static String getTypeName(Type type, FullName fn) {
		if (type instanceof Class){
			return getTypeName((Class<?>) type, fn);
		}else if(type instanceof ParameterizedType){
			return getTypeName((ParameterizedType)type,fn);
		}else{
			return getGenericTypeName(type.toString(), fn);
		}
	}
	/*
	 * Utility routine to paper over array type names
	 */
	public static final String getTypeName(Class<?> type, boolean fullClassName) {
		if (type.isArray()) {
			return new StringBuilder(getTypeName(type.getComponentType(), fullClassName)).append("[]").toString();
		} else
			return fullClassName ? type.getName().replaceAll("\\$", "\\.") : type.getSimpleName();
	}
	public static final String getTypeName(Class<?> type, FullName fn) {
		if (type.isArray()) {
			return new StringBuilder(getTypeName(type.getComponentType(), fn)).append("[]").toString();
		} else
			return fn.isFullName(type) ? type.getName().replaceAll("\\$", "\\.") : type.getSimpleName();
	}
	public static final <T> String getTypeDefine(Type type, T fullNameFlag) {
		FullName fn;
		if (fullNameFlag instanceof FullName) {
			fn = (FullName) fullNameFlag;
		} else if (fullNameFlag instanceof Boolean) {
			fn = toFullName((Boolean) fullNameFlag);
		} else
			throw new IllegalArgumentException(String.format(
					"the arugment fullNameFlag must be instance of Boolean or %s", FullName.class.getName()));
		if (type instanceof Class)
			return getTypeName((Class<?>) type, fn.isFullName((Class<?>) type));
		else if (type instanceof TypeVariable) {
			StringBuilder builder = new StringBuilder();
			builder.append(type.toString());
			Type bound = ((TypeVariable<?>) type).getBounds()[0];
			if (!(bound instanceof Class<?> && ((Class<?>) bound).getName().equals(Object.class.getName()))) {
				builder.append(" extends ").append(getTypeDefine(bound, fn));
			}
			return builder.toString();
		} else if (type instanceof ParameterizedType) {
			return (fullNameFlag instanceof Boolean && (Boolean) fullNameFlag) ? type.toString() : getGenericTypeName(
					type.toString(), fn);
		}
		return type.toString();
	}
	public final static String genericTypeName(String type) {
		return getGenericTypeName(type, FULLNAME_INSTANCE_FALSE);
	}
	/**
	 * 返回一个泛型对象的简单类型<br>
	 * 如:将 {@code java.util.Map<java.lang.String,net.gdface.sdk.CodeInfo[]>}返回 {@code List<String,CodeInfo[]>}
	 * 
	 * @param type
	 * @param fn
	 * @return 类型描述字符串
	 */
	public final static String getGenericTypeName(String type, FullName fn) {
		Matcher match = Pattern.compile("([\\w\\.\\$]+)([<>\\[\\],])").matcher(type);
		StringBuffer buffer = new StringBuffer();
		Class<?> clazz;
		while (match.find()) {
			try {
				clazz = Class.forName(match.group(1).trim());
				match.appendReplacement(buffer, getTypeName(clazz,fn) + "$2");
			} catch (ClassNotFoundException e) {
				match.appendReplacement(buffer, "$1$2");
			}
		}
		match.appendTail(buffer);
		return buffer.toString();
	}
	/**
	 * 返回一个泛型对象的简单类型<br>
	 * 如:将 {@code java.util.Map<java.lang.String,net.gdface.sdk.CodeInfo[]>}返回 {@code List<String,CodeInfo[]>}
	 * 
	 * @param type
	 * @param fn
	 * @return 类型描述字符串
	 */
	public final static String getTypeName(ParameterizedType type, FullName fn) {
		StringBuilder sb = new StringBuilder();
		sb.append(getTypeName(type.getRawType(),fn));
		Type[] actualTypeArguments = type.getActualTypeArguments();
		if (actualTypeArguments != null && actualTypeArguments.length > 0) {
			sb.append("<");
			boolean first = true;
			for (Type t : actualTypeArguments) {
				if (!first) {
					sb.append(", ");
				}
				sb.append(getTypeName(t,fn));
				first = false;
			}
			sb.append(">");
		}
		return sb.toString();
	}
	public final static String[] getAllTypeNamesForGenericType(String type) {
		String[] names = type.replaceAll("([<>\\[\\],?]|\\bextends\\b|\\bsuper\\b)", ",").split(",");
		Set<String> set = new HashSet<String>();
		for (String name : names) {
			if (!(name = name.trim()).isEmpty()) {
				set.add(name);
			}
		}
		return set.toArray(new String[0]);
	}
	public final static Class<?>[] getAllClassForGenericType(String type) {
		Set<Class<?>> set = new HashSet<Class<?>>();
		for (String name : getAllTypeNamesForGenericType(type)) {
			try {
				set.add(Class.forName(name));
			} catch (ClassNotFoundException e) {
	
			}
		}
		return set.toArray(new Class<?>[0]);
	}
	public static final Class<?> getElementClass(Class<?> clazz) {
		return clazz.isArray() ? getElementClass(clazz.getComponentType()) : clazz;
	}
	public static final boolean isJavaLangClass(Class<?> clazz) {
		return null == clazz.getPackage() ? false : "java.lang".equals(clazz.getPackage().getName());
	}
	static final FullName toFullName(boolean fullClassName) {
		return fullClassName ? FULLNAME_INSTANCE_TRUE : FULLNAME_INSTANCE_FALSE;
	}
	public static final String getDocSignature(Method method, boolean fn) {
		return getDocSignature(method, toFullName(fn));
	}

	public final static String getDocSignature(Method method, final Map<String, Class<?>> importedList) {
		FullName fn;
		if (null != importedList) {
			fn = new FullName() {
				@Override
				public boolean isFullName(Class<?> type) {
					return !importedList.containsValue(type);
				}
			};
		} else
			fn = FULLNAME_INSTANCE_TRUE;
		return getDocSignature(method, fn);
	}
	private static String getDocSignature(Method method, FullName fn) {
		StringBuilder builder = new StringBuilder()
				.append(TypeUtils.getTypeName(method.getDeclaringClass(), fn.isFullName(method.getDeclaringClass()))).append("#")
				.append(method.getName()).append("(");
		int c = 0;
		for (Class<?> type : method.getParameterTypes()) {
			if (c++ > 0)
				builder.append(",");
			builder.append(TypeUtils.getTypeName(type, fn.isFullName(type)));
		}
		builder.append(")");
		return builder.toString();
	}
	public static final String getSignature(Method method) {
		return getDocSignature(method, true).replaceFirst("\\S*#", "");
	}

	public static interface FullName {
		boolean isFullName(Class<?> type);
	}
	public static <K,V>V replace(Map<K, V>m,K key, V value) {
		V curValue;
	    if (((curValue = m.get(key)) != null) || m.containsKey(key)) {
	        curValue = m.put(key, value);
	    }
	    return curValue;
	}
	public static <K,V> V putIfAbsent(Map<K, V>m,K key, V value) {
	    V v = m.get(key);
	    if (v == null) {
	        v = m.put(key, value);
	    }	
	    return v;
	}
}


